-- WOW API Compatibility layer
do

	function Dump(name, value)
		local dumped = {}
		
		local tokey = function(i)
			local t = type(i)
			if t == "table" then
				if dumped[i] then
					return "["..dumped[i].."]"
				else
					return "["..tostring(i).."]"
				end
			elseif t == "string" then
				return "[\""..i.."\"]"
			else
				return "["..tostring(i).."]"
			end
		end
		
		local DoDump
		DoDump = function(rname, name, value, level)
			local t = type(value)
			local s = table.concat(level)
			if t == "table" then
				if dumped[value] then
					print(s..tokey(name).." = "..dumped[value])
				else
					print(s..tokey(name).." = {")
					table.insert(level, "\t")
					for k, v in pairs(value) do
						DoDump(rname..tokey(k), k, v, level)
					end
					table.remove(level)
					dumped[value] = rname
					print(s.."}, -- "..name)
				end
			else
				print(s..tokey(name).." = "..tostring(value)..",")
			end
		end
		
		DoDump(name, name, value, {})
	end

	local Roster = {}
	local function DumpRoster( name)
		print("--[[ Roster", name)
		for i, entry in ipairs(Roster) do
			print(i, 
				string.format("%-12s", entry.name), 
				entry.rank, 
				entry.subgroup, 
				entry.level, 
				string.format("%-10s", entry.class), 
				string.format("%-8s", entry.fileName), 
				entry.zone, 
				entry.online, 
				entry.isDead,
				tostring(entry.role or "<none>"),
				entry.loot and "masterloot" or "")
		end
		print("]]")
	end
	
	local Event = {
		map = {},
		schedule = {}
	}
	
	function RegisterEvent(event, callback)
		local map = Event.map[event]
		if not map then
			map = {}
			Event.map[event] = map
		end
		table.insert(map, callback)
	end

	local function ValidateRoster()
		local names = {}
		local counts = {}
		local found
		
		for _, entry in ipairs(Roster) do
			assert(not names[entry.name], "Duplicate name in Roster ("..entry.name..")")
			names[entry.name] = true
			counts[entry.subgroup] = (counts[entry.subgroup] or 0) + 1
			if entry.rank == 2 then found = true end
		end
		
		for g, count in ipairs(counts) do
			assert(count <= 5, "Too many people in subgroup ("..g..")")
		end
		
		assert(found, "Roster has no master !")
	end
	
	function UnregisterEvent(event, callback)
		local map = Event.map[event]
		if map then
			for i, cb in ipairs(map) do
				if cb == callback then
					table.remove(map, i)
					return
				end
			end
		end
	end
	
	local function ScheduleEvent(event)
		for _, e in ipairs(Event.schedule) do
			if e == event then return end
		end
		table.insert(Event.schedule, event)
	end

	function GetRaidRosterInfo(index)
		local entry = Roster[index]
		if entry then
			return entry.name, entry.rank, entry.subgroup, entry.level, 
				entry.class, entry.fileName, entry.zone, entry.online,
				entry.isDead, entry.role, entry.loot
		end
	end
	
	function SwapRaidSubgroup(a, b)
		print(string.format("SwapRaidSubgroup(%d, %d)", a, b))
		local entry_a, entry_b = Roster[a], Roster[b]
		if entry_a and entry_b then
			local ga, gb = entry_a.subgroup, entry_b.subgroup
			if ga ~= gb then
				entry_a.subgroup, entry_b.subgroup = gb, ga
				ScheduleEvent("RAID_ROSTER_UPDATE")
				ValidateRoster()
			end
		end
	end
	
	function SetRaidSubgroup(a, g)
		print(string.format("SetRaidSubgroup(%d, %d)", a, g))
		local entry = Roster[a]
		if not entry or entry.subgroup == g then return end
		local count = 0
		for _, e in ipairs(Roster) do
			if e.subgroup == g then
				count = count + 1
			end
		end
		if count >= 5 then
			return
		end
		entry.subgroup = g
		ScheduleEvent("RAID_ROSTER_UPDATE")
		ValidateRoster()
	end
	
	function PromoteToAssistant(name)
		for _, e in ipairs(Roster) do
			if e.name == name and e.rank == 0 then
				e.rank = 1
				ScheduleEvent("RAID_ROSTER_UPDATE")
				ValidateRoster()
				return
			end
		end
	end

	function SetLootMethod(method, name, threshold)
	end

	function MainLoop()
		ScheduleEvent("INITIALIZE")
		while true do
			-- Dump("EVENTS", Event.schedule)
			local event = table.remove(Event.schedule, 1)
			if not event then
				print("mainloop finished")
				DumpRoster("RosterEnd")
				return
			end
			local map = Event.map[event]
			if map then
				for _, entry in ipairs(map) do
					entry[event](entry)
				end
			end
		end
	end

	local CLASS = {
		WARRIOR = "Guerrier",
		PRIEST = "Pretre",
		DRUID = "Druide",
		PALADIN = "Paladin",
		DEMONIST = "Demoniste",
		SHAMAN = "Shaman",
		ROGUE = "Voleur",
		MAGE = "Mage",
		HUNTER = "Chasseur",
	}
	
	function SetRaidRoster1()
		Roster = {
			{ name = "Macleod", rank = 1, subgroup = 1, fileName = "WARRIOR" },
			{ name = "Chdje", rank = 1, subgroup = 1, fileName = "PRIEST" },
			{ name = "Jerry", rank = 2, subgroup = 1, fileName = "DRUID" },
			{ name = "Toff", rank = 1, subgroup = 1, fileName = "PALADIN" },
			{ name = "Flitz", rank = 0, subgroup = 1, fileName = "DEMONIST" },

			{ name = "Apophis", rank = 0, subgroup = 2, fileName = "WARRIOR" },
			{ name = "Agarwaen", rank = 0, subgroup = 2, fileName = "PRIEST" },
			{ name = "Milambert", rank = 0, subgroup = 2, fileName = "DRUID" },
			{ name = "Tonus", rank = 0, subgroup = 2, fileName = "PALADIN" },
			{ name = "Aphrodysia", rank = 0, subgroup = 2, fileName = "DEMONIST" },

			{ name = "Dieuguile", rank = 1, subgroup = 3, fileName = "WARRIOR" },
			{ name = "Suri", rank = 0, subgroup = 3, fileName = "PRIEST" },
			{ name = "Eliijah", rank = 0, subgroup = 3, fileName = "DRUID" },
			{ name = "Georim", rank = 0, subgroup = 3, fileName = "PALADIN" },
			{ name = "Demaquila", rank = 0, subgroup = 3, fileName = "DEMONIST" },

			{ name = "Radnarok", rank = 0, subgroup = 4, fileName = "WARRIOR" },
			{ name = "Kastaur", rank = 0, subgroup = 4, fileName = "PRIEST" },
			{ name = "Onserei", rank = 0, subgroup = 4, fileName = "DRUID" },
			{ name = "Thanator", rank = 0, subgroup = 4, fileName = "PALADIN" },
			{ name = "Targezed", rank = 1, subgroup = 4, fileName = "DEMONIST" },

			{ name = "Millander", rank = 1, subgroup = 5, fileName = "MAGE" },
			{ name = "Orty", rank = 0, subgroup = 5, fileName = "MAGE" },
			{ name = "Ekkaia", rank = 0, subgroup = 5, fileName = "MAGE" },
			{ name = "Grandspieds", rank = 0, subgroup = 5, fileName = "MAGE" },
			{ name = "Magmandar", rank = 0, subgroup = 5, fileName = "MAGE" },

			{ name = "Khaldyr", rank = 0, subgroup = 6, fileName = "ROGUE" },
			{ name = "Jarbir", rank = 0, subgroup = 6, fileName = "ROGUE" },
			{ name = "Yogsothtoth", rank = 0, subgroup = 6, fileName = "ROGUE" },
			{ name = "Buzlclair", rank = 0, subgroup = 6, fileName = "ROGUE" },
			{ name = "Stalion", rank = 0, subgroup = 6, fileName = "ROGUE" },

			{ name = "Belze", rank = 0, subgroup = 7, fileName = "HUNTER" },
			{ name = "Romulus", rank = 0, subgroup = 7, fileName = "HUNTER" },
			{ name = "Absalom", rank = 0, subgroup = 7, fileName = "HUNTER" },
			{ name = "Lounatrila", rank = 0, subgroup = 7, fileName = "HUNTER" },
			{ name = "Soulai", rank = 0, subgroup = 7, fileName = "HUNTER" },
			
			{ name = "Andarhun", rank = 0, subgroup = 8, fileName = "WARRIOR" },
			{ name = "Drasilia", rank = 0, subgroup = 8, fileName = "DRUID" },
			{ name = "Yesus", rank = 0, subgroup = 8, fileName = "PRIEST" },
			{ name = "Fromaz", rank = 0, subgroup = 8, fileName = "MAGE" },
		}
		for _, entry in ipairs(Roster) do
			entry.level = 60
			entry.class = CLASS[entry.fileName]
			entry.zone = ""
			entry.online = true
			entry.isDead = false
		end
		ValidateRoster()
		Roster[1].role = "maintank"
		Roster[31].role = "mainassist"
		Roster[4].loot = true
		DumpRoster("RosterTemplate")
	end

	function SetRaidRoster2()
		for _, entry in ipairs({
			{ source = "Andarhun", dest = "Daenerys", class = "MAGE" },
			{ source = "Flitz", dest = "Littlemouche" },
			-- { source = "Macleod", dest = nil },
			{ dest = "Chtun", class = "SHAMAN" },
				}) do
			
			if entry.source then
				for i, e in ipairs(Roster) do
					if e.name == entry.source then
						if entry.dest then
							e.name = entry.dest
							if entry.class then 
								e.fileName = entry.class 
							end
						else
							table.remove(Roster, i)
						end
						break
					end
				end
			else
				table.insert(Roster, { name = entry.dest, rank = 0, subgroup = 0, fileName = entry.class })
			end
		end
		
		table.sort(Roster, function (a, b) return a.name < b.name end)
		for i, entry in ipairs(Roster) do
			entry.level = 60
			entry.class = CLASS[entry.fileName]
			entry.zone = ""
			entry.online = true
			entry.isDead = false
			entry.subgroup = 1 + math.floor((i-1) / 5)
			if entry.rank == 1 then
				entry.rank = 0
			end
		end
		
		ValidateRoster()
		DumpRoster("RosterStart")
	end
end

local RaidTemplate = {}

function RaidTemplate:Print(s, ...)
	if s:find("%", nil, true) then
		s = string.format(s, ...)
	end
	print(s)
end

function RaidTemplate:RegisterEvent(event)
	assert(type(self[event]) == "function")
	RegisterEvent(event, self)
end

function RaidTemplate:UnregisterEvent(event)
	UnregisterEvent(event, self)
end

function RaidTemplate:INITIALIZE()
	SetRaidRoster1()
	local template = self:BuildTemplate()
	Dump("template", template)
	SetRaidRoster2()
	self:ApplyTemplate(template, true, true, true)
end

RaidTemplate:RegisterEvent("INITIALIZE")

--[[

local raidtemplate = {
	classes = {
		PRIEST = { 
			<priest1> = 1, 
			<priest2> = 2,
			<priest3> = 3
		},
	},
	ranks = {
		<priest1> = 2,
	},
	roles = {
		<warrior1> = "maintank",
		
	},
	groups = {
		[1] = {
			PRIEST = 1,
			WARRIOR = 1,
		}
	},
}
]]

local function GetOrderedRosterList()
	local temp = {}
	
	for i = 1, 40 do
		local name, _, subgroup, _, _, class = GetRaidRosterInfo(i)
		if name then
			temp[#temp + 1] = {
				name = name,
				subgroup = subgroup,
				class = class
			}
		end
	end

	local comp = function (a, b)
		if a.subgroup < b.subgroup then
			return true
		elseif a.subgroup > b.subgroup then
			return false
		elseif a.class < b.class then
			return true
		elseif a.class > b.class then
			return false
		else
			return a.name < b.name
		end
	end

	table.sort(temp, comp)
	
	return temp
end
	
function RaidTemplate:BuildTemplate()
	local template = { classes = {}, groups = {}, ranks = {}, roles = {}, loot = {} }
	
	local list = GetOrderedRosterList()
	
	for i, entry in ipairs(list) do
		local class = entry.class
		local c = template.classes[class]
		if not c then
			c = {}
			template.classes[class] = c
		end
		table.insert(c, entry.name)
		
		local subgroup = entry.subgroup
		local g = template.groups[subgroup]
		if not g then
			g = {}
			template.groups[subgroup] = g
		end
		g[class] = (g[class] or 0) + 1
	end
	
	for i = 1, 40 do
		local name, rank, _, _, _, _, _, _, _, role, loot = GetRaidRosterInfo(i)
		
		if name then
			if rank == 1 then
				template.ranks[name] = true
			end
			
			if role then
				template.roles[name] = role
			end
			
			if loot then
				template.loot = name
			end
			
		end
	end
	
	return template
end

local function GetClassRosterList(prefered)
	local temp = {}
	for i = 1, 40 do
		local name, _, _, _, _, class = GetRaidRosterInfo(i)
		if name then
			local c = temp[class]
			if not c then
				c = {}
				temp[class] = c
			end
			local order = 1000 + i
			if prefered[class] then
				for i, n in ipairs(prefered[class]) do
					if name == n then
						order = i
						break
					end
				end
			end
			c[#c+1] = {
				name = name,
				order = order
			}
		end
	end
	for _, c in pairs(temp) do
		table.sort(c, function (a, b) return a.order < b.order end)
	end
	
	local result = {}
	for class, l in pairs(temp) do
		local r = {}
		result[class] = r
		for i, entry in ipairs(l) do
			r[i] = entry.name
		end
	end
	
	return result
end

local function MakeBuild(template)
	local list = GetClassRosterList(template.classes)
	
	-- Dump("GetClassRosterList", list)
	
	local build= {}
	
	for g, composition in ipairs(template.groups) do
		local group = {}
		build[g] = group
		for class, count in pairs(composition) do
			local players = list[class] or {}
			for i = 1, count do
				local name
				if players[1] then
					name = players[1]
					table.remove(players, 1)
				else
					name = "*"..class
				end
				table.insert(group, name)
			end
		end
	end
	
	for _, group in pairs(build) do
		local i = 1
		while group[i] do
			local name = group[i]
			if name:find("*", nil, true) then
				local found = false
				if list[class] then
					for _, l in ipairs(list[class]) do
						if l[1] then
							group[i] = l[1]
							table.remove(l)
							found = true
							break
						end
					end
				end
				if not found then 
					print("Unable to fill for", name)
					table.remove(group, i)
				else
					i = i + 1
				end
			else
				i = i + 1
			end
		end
	end
	
	-- fill in the remaining players
	for class, players in pairs(list) do
		for _, name in ipairs(players) do
			for i = 1, 8 do
				local group = build[i]
				if not group then
					group = {}
					build[i] = group
				end
				if #group < 5 then
					table.insert(group, name)
					break
				end
			end
		end
	end
	
	Dump("build-list", build)
	
	return build
end

local function ConvertBuildToIndex(build)
	local groups = {}
	local indexes = {}
	for i = 1, 40 do
		local name, _, group = GetRaidRosterInfo(i)
		if name then
			indexes[name] = i
			groups[name] = group
		end
	end
	
	local result = {
		source = {},
		dest = {},
	}
	
	for gi, group in pairs(build) do
		for i, name in pairs(group) do
			local index = indexes[name]
			if index then
				local value  = { name = name, index = index, source = groups[name], dest = gi }
				
				local s = result.source[value.source]
				if not s then
					s = {}
					result.source[value.source] = s
				end
				s[name] = value
				
				local d = result.dest[value.dest]
				if not d then
					d = {}
					result.dest[value.dest] = d
				end
				d[name] = value
			end
		end
	end

	return result
end

function RaidTemplate:ApplyTemplateStep()
	self:UnregisterEvent("RAID_ROSTER_UPDATE")
	if not self.apply then return end
	local build = MakeBuild(self.apply.template)
	build = ConvertBuildToIndex(build)
	Dump("build", build)
	
	local missed = false
	
	for i, group in pairs(build.dest) do
		for _, entry in pairs(group) do
			if entry.source ~= i then
				-- we need to move this one
				-- first try to swap with someone from source group
				for _, other in pairs(build.source[entry.dest]) do
					if other.dest == entry.source then -- found one !
						self:RegisterEvent("RAID_ROSTER_UPDATE")
						SwapRaidSubgroup(entry.index, other.index)
						return
					end
				end
				-- next check if the destination group is full, and move the unit if not
				local count = 0
				for name in pairs(build.source[entry.dest]) do
					count = count + 1
				end
				if count < 5 then
					self:RegisterEvent("RAID_ROSTER_UPDATE")
					SetRaidSubgroup(entry.index, entry.dest)
					return
				end
				-- find a unit in the destination group that is misplaced, and swap with it
				for _, other in pairs(build.source[entry.dest]) do
					if other.dest ~= other.source then
						self:RegisterEvent("RAID_ROSTER_UPDATE")
						SwapRaidSubgroup(entry.index, other.index)
						return
					end
				end
				-- ok, nothing worked... let's continue...
				missed = true
			end
		end
	end
	
	if missed then
		self:Print("RaidTemplate not set ! Bailing out, sorry :-(")
	end
	
	-- we have not updated anything, so last steps and stop
	if self.apply.ranks then
		for name in pairs(self.apply.template.ranks) do
			PromoteToAssistant(name)
		end
	end
	if self.apply.roles then
		-- ???
	end
	if self.apply.loot then
		SetLootMethod("master", self.apply.template.loot, self.apply.threshold)
	end
	self:Print(string.format("RaidTemplate set ! Used %d Kb and performed %d operations", collectgarbage("count") - self.apply.count, self.apply.count))
	self.apply = nil
	collectgarbage("collect")
end

function RaidTemplate:RAID_ROSTER_UPDATE()
	if self.apply then
		self.apply.count = self.apply.count + 1
		self:ApplyTemplateStep()
	end
end

function RaidTemplate:ApplyTemplate(template, ranks, roles, loot, threshold)
	collectgarbage("collect")
	self.apply = {
		template = template, 
		ranks = ranks and template.ranks, 
		roles = roles and template.roles,
		loot = loot and template.loot,
		threshold = threshold,
		start = collectgarbage("count"),
		count = 0,
	}
	
	self:ApplyTemplateStep()
end


MainLoop()
